
#ifndef __COMPILER_SYMBOLTABLE_HPP__
#define __COMPILER_SYMBOLTABLE_HPP__

#include <string>
#include <vector>
#include <map>
#include <climits>
#include <cassert>

class Symbol
{
public:

  Symbol( const Symbol& symbol )
  : _name( symbol._name ),
    _level( symbol._level ),
    _type( symbol._type ),
    _offset( symbol._offset ),
    _class( symbol._class ),
    _arrayStart( symbol._arrayStart ),
    _arrayEnd( symbol._arrayEnd )
  {}

  /** Default constructor */
  Symbol()
  : _name( "UNINITIALIZED" ),
    _level( UINT_MAX ),
    _type( "UNINITIALIZED" ),
    _offset( UINT_MAX ),
    _class( "UNINITIALIZED" ),
    _arrayStart( 0 ),
    _arrayEnd( 0 )
   {}

  /** Parameterized constuctor */
  Symbol( const std::string& name,
          unsigned int level,
          const std::string& type,
          const std::string& myClass,
          int offset )
  : _name( name ),
    _level( level ),
    _type( type ),
    _offset( offset ),
    _class( myClass ),
    _arrayStart( 0 ),
    _arrayEnd( 0 )
   {}

  // Setters

  void SetOffset( unsigned int offset ) { _offset = offset; }
  void SetType( const std::string& type ) { _type = type; }

  // Getters

  const std::string& GetName() const { return _name; }
  unsigned int GetLevel() const { return _level; }
  unsigned int GetOffset() const { return _offset; }
  const std::string& GetType() const { return _type; }

  const std::string& GetClass() const { return _class; }

  void SetArrayLimits( unsigned int start, unsigned int end ) { _arrayStart = start; _arrayEnd = end; }
  unsigned int GetArrayStart() { return _arrayStart; }
  unsigned int GetArrayEnd() { return _arrayEnd; }

private:

  std::string  _name;
  unsigned int _level;
  std::string  _type;
  int          _offset;
  std::string  _class;
  unsigned int _arrayStart, _arrayEnd;
};

class SymbolTable
{
  class NotContainedException {}; /**< Exception thrown if a table doesn't contain a certain symbol. */
  class ScopeException {}; /**< Exception thrown if user misplaces scope delimiters */

  typedef std::map< std::string, Symbol > SymbolMap; /**< Tree type */
  typedef std::vector< SymbolMap > SymbolList;       /**< Tree List type */

public:

  SymbolTable()
  : _currentLevel( 0 ),
    _symbolList()
  {
    // Creates the global scope
    _symbolList.push_back( SymbolMap() );
  }

  unsigned int GetCurrentLevel() { return _currentLevel; }

  /** Installs a new symbol */
  Symbol& AddSymbol( const std::string& name, const std::string& type, const std::string& myClass, int offset )
  {
    std::clog << "Adding Symbol: " << name << " to level " << _currentLevel << std::endl;
    return this->AddSymbol( name, _currentLevel, type, myClass, offset );
  }

  /** Retrieves an installed symbol.
   * @throws error if user attempts to retrieve an unexistent symbol.
   */
  Symbol& GetSymbol( const std::string& name )
  {
    Symbol* symbol = NULL;

    for( int i = _currentLevel; i >= 0; --i )
    {
      symbol = this->GetSymbol( name, i );
      if( symbol )
        return *symbol;
    }

    std::cerr << "ERROR - Symbol: " << name << " not contained!\n";
    std::cerr << "  Current Level: " << _currentLevel << std::endl;
    throw NotContainedException();
  }

  void EnterBlock()
  {
    std::cerr << "Enter!\n";

    if( _currentLevel == UINT_MAX )
      throw ScopeException();

    _currentLevel++;

    // Creates a new symbol level if necessary.
    if( _symbolList.size() <= _currentLevel )
      _symbolList.push_back( SymbolMap() );
  }
  void ExitBlock()
  {
    _symbolList[_currentLevel].clear();

    if( _currentLevel == 0 )
      throw ScopeException();

    _currentLevel--;
  }

protected:

  Symbol& AddSymbol( const std::string& name, unsigned int level, const std::string& type, const std::string& myClass, int offset  )
  {
    Symbol symbol( name, level, type, myClass, offset );

    assert( _symbolList.size() > level );

    SymbolMap::iterator i = _symbolList[level].find( name );
    assert( i == _symbolList[level].end() );

    _symbolList[level][name] = symbol;
    return _symbolList[level][name];
  }

  Symbol* GetSymbol( const std::string& name, unsigned int level ) throw( NotContainedException )
  {
    if( _symbolList.size() <= level ) return NULL;

    SymbolMap::iterator i = _symbolList[level].find( name );

    if( i == _symbolList[level].end() ) return NULL;

    return &(i->second);
  }

private:

  unsigned int _currentLevel;
  SymbolList   _symbolList;
};

#endif // __COMPILER_SYMBOLTABLE_HPP__
